package com.nulgame.bukkit.plugin;

import java.io.File;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.logging.Logger;

import org.bukkit.ChatColor;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.bukkit.event.player.AsyncPlayerChatEvent;
import org.bukkit.plugin.PluginManager;
import org.bukkit.plugin.java.JavaPlugin;
import org.bukkit.scheduler.BukkitRunnable;

import com.nulgame.bukkit.plugin.model.AntiSpamConfiguration;
import com.nulgame.bukkit.plugin.model.ChatInfo;
import com.nulgame.bukkit.plugin.resource.Resource;
import com.nulgame.bukkit.plugin.responsebuilder.ResponseBuilderFactory;
import com.nulgame.bukkit.plugin.task.PlayerChatCleanerTask;
import com.nulgame.bukkit.plugin.util.Util;
import com.nulgame.bukkit.plugins.listener.AntiSpamListener;

public class AntiSpamPlugin extends JavaPlugin {
	private static final Logger m_log = Logger.getLogger("AntiSpamPlugin");
	private File m_configFile;
	private AntiSpamConfiguration m_configuration = new AntiSpamConfiguration();
	private Map<Player, Map<String, ChatInfo>> m_recentPlayerChatMap = new ConcurrentHashMap<Player, Map<String, ChatInfo>>(new LinkedHashMap<Player, Map<String,ChatInfo>>());

	private Set<Player> m_cleanedPlayerCandidateMinor = new CopyOnWriteArraySet<Player>();
	private Set<Player> m_cleanedPlayerCandidateMajor = new CopyOnWriteArraySet<Player>();
	private Set<Player> m_cleanedPlayerCandidateCritical = new CopyOnWriteArraySet<Player>();
	
	private Map<Player, Long> m_playerLastTimeChat = new LinkedHashMap<Player, Long>();
	
	private Long m_latestChatTimeMillis = 0L;
	private Long m_numberOfChat = 0L;
	private BukkitRunnable m_task;
	
	@Override
	public void onEnable() {
		m_log.info("Running on " + System.getProperty("os.name"));
		PluginManager pm = getServer().getPluginManager();
		pm.registerEvents(new AntiSpamListener(this), this);
		initialize();
		m_task = new PlayerChatCleanerTask(this);
		m_task.runTaskLaterAsynchronously(this, 60*20);
	}
	@Override
	public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
		return false;
	}
	
	public synchronized void onChat(AsyncPlayerChatEvent event){
		Player player = event.getPlayer();
		String chat = event.getMessage();
		Long time = System.currentTimeMillis();
		//anti-overflow
		if(m_configuration.options.antiChatOverFlow && !antiChatOverFlow(player, time)){
			event.setCancelled(true);
			String response = new ResponseBuilderFactory(m_configuration)
									.createAntiChatOverFlowResponseBuilder(player.getName(), chat).toResponse();
			if(response != null){
				sendErrorToPlayer(player, response, m_configuration.antiChatOverFlow.responseTextColor);
			}
			return;
		}
		//anti-spam message
		if(m_configuration.options.antiChatSpam && !antiChatSpam(player, chat, time)){
			event.setCancelled(true);
			String response = new ResponseBuilderFactory(m_configuration)
									.createAntiSpamResponseBuilder(player.getName(), chat).toResponse();
			if(response != null){
				sendErrorToPlayer(player, response, m_configuration.antiChatSpam.responseTextColor);
			}
			return;
		}
		//anti-word
		if(m_configuration.options.antiWord){
			String violativedWord = antiWord(chat);
			if(violativedWord != null){
				event.setCancelled(true);
				String response = new ResponseBuilderFactory(m_configuration)
										.createAntiWordResponseBuilder(player.getName(), chat, violativedWord).toResponse();
				if(response != null){
					sendErrorToPlayer(player, response, m_configuration.antiWord.responseTextColor);
				}
				return;
			}
		}
		//anti-cap
		if(m_configuration.options.antiWordCap && !antiWordCap(chat)){
			event.setCancelled(true);
			String response = new ResponseBuilderFactory(m_configuration)
									.createAntiWordCapResponseBuilder(player.getName(), chat).toResponse();
			if (response != null) {
				sendErrorToPlayer(player, response, m_configuration.antiWordCap.responseTextColor);
			}
			return;

		}
		//anti-ip address
		if(m_configuration.options.antiIPAddress){
			String ip = antiIpAddress(chat);
			if(ip != null){
				event.setCancelled(true);
				String response = new ResponseBuilderFactory(m_configuration)
						.createAntiIPAddressResponseBuilder(player.getName(), chat, ip).toResponse();
				if (response != null) {
					sendErrorToPlayer(player, response, m_configuration.antiIPAddress.responseTextColor);
				}
				return;
			}
			
		}
		// anti-server
		if (m_configuration.options.antiServerAddress ) {
			String serverAddress = antiServerAddress(chat);
			if (serverAddress != null) {
				event.setCancelled(true);
				String response = new ResponseBuilderFactory(m_configuration)
						.createAntiServerAddressResponseBuilder(player.getName(), chat, serverAddress).toResponse();
				if (response != null) {
					sendErrorToPlayer(player, response, m_configuration.antiServerAddress.responseTextColor);
				}
				return;
			}
		}
		//replacement
		if(m_configuration.options.replacement){			
			chat = replaceWord(chat);
			event.setMessage(chat);
		}
	}
	private String replaceWord(String chat) {
		for(int i =0 ; i < m_configuration.replacement.left.size(); i++){
			String left = m_configuration.replacement.left.get(i);
			String right = m_configuration.replacement.right.get(i);
			chat = chat.replaceAll(left, right);
		}
		return chat;
	}
	
	private String antiIpAddress(String chat) {
		String[] words = chat.split(Resource.SPACE_COLON_PATERN);
		for(String word : words){
			if(Util.validIP(word.trim())){
				return word;
			}
		}
		return null;
	}
	private boolean antiWordCap(String chat) {		

		String wordWithOudSpace =  chat.trim();
		int length = wordWithOudSpace.length();
		if(length > 0){
			int numbderOfCap = Util.countCapitalChar(wordWithOudSpace);
			Double percentage = (100.0)*numbderOfCap/length;
			return percentage < m_configuration.antiWordCap.allowedPercentage;
		}		
		return true;
	}
	private boolean antiChatOverFlow(Player player, Long time) {
		Long lastTimeChat = m_playerLastTimeChat.get(player);
		m_playerLastTimeChat.put(player, time);
		if(lastTimeChat != null){
			if(time - lastTimeChat <= m_configuration.antiChatOverFlow.inMillisTime){
				return false;
			}
		}
		return true;
	}
	
	private String antiWord(String chat){
		if(!m_configuration.antiWord.words.isEmpty()){			
			String[] words = chat.split(Resource.SAPCE);
			for (String word : words) {
				if (m_configuration.antiWord.words.contains(word)) {
					return word;
				}
			}
		}
		return null;
	}
	
	private String antiServerAddress(String chat){
		if(!m_configuration.antiServerAddress.domainTypes.isEmpty()){
			
			String[] words = chat.split(Resource.SPACE_COLON_PATERN);
			
			for(String word : words) {
				String[] parts = word.split(Resource.DOT_PATERN);
				for(String part : parts){
					if(m_configuration.antiServerAddress.domainTypes.contains(part)){
						return word;
					}
				}
			}
		}
		return null;
	}
	
	private void sendErrorToPlayer(Player player, String response, ChatColor color) {
		player.sendMessage(color + response);
	}
	private boolean antiChatSpam(Player player, String chat, Long time) {
		synchronized (m_numberOfChat) {
			m_numberOfChat++;
		}
		Map<String, ChatInfo> playerMessageMap = m_recentPlayerChatMap.get(player);
		if(playerMessageMap == null){
			playerMessageMap = new ConcurrentHashMap<String, ChatInfo>();
			m_recentPlayerChatMap.put(player, playerMessageMap);
		}
		if(playerMessageMap.size() >= 3){
			m_cleanedPlayerCandidateMinor.add(player);
		}else if(playerMessageMap.size() >= 10){
			m_cleanedPlayerCandidateMinor.remove(player);
			m_cleanedPlayerCandidateMajor.add(player);
		}
		else if(playerMessageMap.size() >= 30){
			m_cleanedPlayerCandidateMajor.remove(player);
			m_cleanedPlayerCandidateCritical.add(player);
		}
		
		ChatInfo chatInfo = playerMessageMap.get(chat);
		if(chatInfo == null){
			
			chatInfo = new ChatInfo();
			chatInfo.total = 1L;
			chatInfo.beginTimeMillis = time;
			playerMessageMap.put(chat, chatInfo);
			
		}else{
			
			Long beginTimeMillis = 0L;
			synchronized (chatInfo.beginTimeMillis) {
				beginTimeMillis = chatInfo.beginTimeMillis;
			}
			if(time - beginTimeMillis >= m_configuration.antiChatSpam.inMillisTime){
				synchronized (chatInfo.total) {
					synchronized (m_numberOfChat) {
						m_numberOfChat-= chatInfo.total;
					}
					chatInfo.total = 1L;
				}
				synchronized (chatInfo.beginTimeMillis) {
					chatInfo.beginTimeMillis = time;
				}
			}else{
				synchronized (chatInfo.total) {
					chatInfo.total++;
				}
			}
			Long total = 0L;
			synchronized (chatInfo.total) {
				total = chatInfo.total;
			}
			
			if(total > m_configuration.antiChatSpam.allowedInfraction){
				return false;
			}
			
		}
		synchronized (chatInfo.lastTimeMillis) {
			chatInfo.lastTimeMillis =  time;
		}
		synchronized (m_latestChatTimeMillis) {
			m_latestChatTimeMillis = time;
		}
		return true;
	}
	
	public synchronized void onLeave(Player player){
		m_playerLastTimeChat.remove(player);
		Map<String, ChatInfo> chatInfoMap = null;
		synchronized (m_recentPlayerChatMap) {
			chatInfoMap = m_recentPlayerChatMap.get(player);
		}
		if(chatInfoMap != null ){
			synchronized (m_numberOfChat) {
				m_numberOfChat -= chatInfoMap.size();
			}
			m_recentPlayerChatMap.remove(player);
			boolean removed = m_cleanedPlayerCandidateCritical.remove(player) 
								|| m_cleanedPlayerCandidateMajor.remove(player) 
								|| m_cleanedPlayerCandidateMinor.remove(player);
		}
	}
	
	private synchronized void initialize(){
		m_configFile = new File(getDataFolder(), Resource.CUSTOM_FILE_NAME);
		if(!m_configFile.exists()){
			saveDefaultConfig();
		}
		synchronized (AntiSpamConfiguration.class) {
			m_configuration = loadConfig(m_configFile);
		}
	}
	
	private AntiSpamConfiguration loadConfig(File configFile){
		return Util.loadConfig(configFile);
	}
	
	public Map<Player, Map<String, ChatInfo>> getRecentPlayerChatMap() {
		return m_recentPlayerChatMap;
	}
	public Set<Player> getCleanedPlayerCandidateCritical() {
		return m_cleanedPlayerCandidateCritical;
	}
	public Long getAntiChatSpamLimitedTimeMillis(){
		return m_configuration.antiChatSpam.inMillisTime;
	}
	//for UT only
	void setRecentPlayerChatMap(Map<Player, Map<String, ChatInfo>> m_recentPlayerChatMap) {
		this.m_recentPlayerChatMap = m_recentPlayerChatMap;
	}
	void setConfiguration(AntiSpamConfiguration configuration){
		m_configuration = configuration;
	}
	void setPlayerLastTimeChat(Map<Player, Long> playerLastChatTime){
		m_playerLastTimeChat = playerLastChatTime;
	}
}

